﻿namespace Nomo.UnityCoreModule.UI
{
    /// <summary>
    /// 用于挂载到 <see cref="UnityEngine.UI.ScrollRect"/> 的 <see cref="UnityEngine.UI.ScrollRect.content"/> 的子物体上，以便在其进入或退出 <see cref="UnityEngine.UI.ScrollRect"/> 所处区域时得到通知。
    /// </summary>
    [UnityEngine.DisallowMultipleComponent]
    [UnityEngine.RequireComponent(typeof(UnityEngine.RectTransform))]
    public sealed class ScrollRectContentChild : UnityEngine.EventSystems.UIBehaviour, UnityEngine.UI.ICanvasElement
    {
        /// <summary>
        /// 为 <see cref="ScrollRectContentChild"/> 进入或退出 <see cref="ScrollRectContentChild.scrollRect"/> 所处区域事件提供数据。
        /// </summary>
        public sealed class IsInChangedEventArgs : System.EventArgs
        {
            /// <summary>
            /// 此物体与 <see cref="ScrollRectContentChild.scrollRect"/> 或处区域有重叠部分吗？
            /// </summary>
            public bool IsIn { get; set; }
        }

        [UnityEngine.SerializeField] private UnityEngine.UI.ScrollRect scrollRect;

        private bool _isIn;

        /// <summary>
        /// 退出或进入 <see cref="scrollRect"/> 所处区域。
        /// </summary>
        public event System.EventHandler<IsInChangedEventArgs> IsInChanged;

        private void InvokeIsInChanged()
        {
            if (IsDestroyed() || scrollRect.IsDestroyed())
            {
                return;
            }

            var isIn = _isIn;
            if ((_isIn = IsInWithoutCheck()) != isIn)
            {
                IsInChanged?.Invoke(this, new IsInChangedEventArgs {IsIn = _isIn});
            }
        }

        private bool IsInWithoutCheck()
        {
            var rectTransform         = (UnityEngine.RectTransform) transform;
            var rect                  = rectTransform.rect;
            var rectMin               = (UnityEngine.Vector3) rect.min;
            var rectMax               = (UnityEngine.Vector3) rect.max;
            var matrix4X4             = UnityEngine.Matrix4x4.TRS(rectTransform.position, UnityEngine.Quaternion.identity, rectTransform.lossyScale);
            var identityWorldCornerLL = matrix4X4.MultiplyPoint3x4(rectMin);
            var identityWorldCornerRR = matrix4X4.MultiplyPoint3x4(rectMax);
            var identityWorldRect     = UnityEngine.Rect.MinMaxRect(identityWorldCornerLL.x, identityWorldCornerLL.y, identityWorldCornerRR.x, identityWorldCornerRR.y);

            var scrollRectRectTransform         = (UnityEngine.RectTransform) scrollRect.transform;
            var scrollRectRect                  = scrollRectRectTransform.rect;
            var scrollRectRectMin               = (UnityEngine.Vector3) scrollRectRect.min;
            var scrollRectRectMax               = (UnityEngine.Vector3) scrollRectRect.max;
            var scrollRectMatrix4X4             = UnityEngine.Matrix4x4.TRS(scrollRectRectTransform.position, UnityEngine.Quaternion.identity, scrollRectRectTransform.lossyScale);
            var scrollRectIdentityWorldCornerLL = scrollRectMatrix4X4.MultiplyPoint3x4(scrollRectRectMin);
            var scrollRectIdentityWorldCornerRR = scrollRectMatrix4X4.MultiplyPoint3x4(scrollRectRectMax);
            var scrollRectIdentityWorldRect     = UnityEngine.Rect.MinMaxRect(scrollRectIdentityWorldCornerLL.x, scrollRectIdentityWorldCornerLL.y, scrollRectIdentityWorldCornerRR.x, scrollRectIdentityWorldCornerRR.y);

            return identityWorldRect.width >= 0F && identityWorldRect.height >= 0F && scrollRectIdentityWorldRect.width >= 0F && scrollRectIdentityWorldRect.height >= 0F && identityWorldRect.Overlaps(scrollRectIdentityWorldRect);
        }

        void UnityEngine.UI.ICanvasElement.Rebuild(UnityEngine.UI.CanvasUpdate executing) { }

        void UnityEngine.UI.ICanvasElement.LayoutComplete()
        {
            InvokeIsInChanged();
        }

        void UnityEngine.UI.ICanvasElement.GraphicUpdateComplete() { }

        private void OnScrollRectValueChanged(UnityEngine.Vector2 normalizedPosition)
        {
            InvokeIsInChanged();
        }

        /// <inheritdoc />
        protected override void OnEnable()
        {
            base.OnEnable();
            _isIn = false;
            if (!scrollRect.IsDestroyed())
            {
                scrollRect.onValueChanged.AddListener(OnScrollRectValueChanged);
            }
            UnityEngine.UI.CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
        }

        /// <inheritdoc />
        protected override void OnDisable()
        {
            UnityEngine.UI.CanvasUpdateRegistry.UnRegisterCanvasElementForRebuild(this);
            if (!scrollRect.IsDestroyed())
            {
                scrollRect.onValueChanged.RemoveListener(OnScrollRectValueChanged);
            }
            _isIn = false;
            base.OnDisable();
        }
    }
}
